package com.ilikesoup.instant.logger;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

public class ListMonitor extends Monitor implements MethodInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(BeanMonitor.class);

    private Set<String> writerMethods;

    private String name;

    public static <T extends Collection<?>> T wrap(T collection, String name, String... writerMethods) {
        if (collection == null) {
            return null;
        }
        try {
            ListMonitor interceptor = new ListMonitor();
            Enhancer e = new Enhancer();
            e.setSuperclass(collection.getClass());
            e.setCallback(interceptor);
            Object bean = e.create();
            interceptor.writerMethods = new HashSet<>(Arrays.asList(writerMethods));
            interceptor.toStrFunc = Objects::toString;
            interceptor.propertyChangeSupport = new PropertyChangeSupport(bean);
            interceptor.propertyChangeSupport.addPropertyChangeListener(new CollectionPropertyChangeLogListener());
            interceptor.name = name;
            ((Collection)bean).addAll(collection);
            return (T) bean;
        } catch (Throwable e) {
            logger.error("BeanMonitor's creation failed!", e);
            return collection;
        }
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        Object retValFromSuper = null;
        String oldVal = null;
        boolean isWriterMethod = writerMethods.contains(method.getName());
        try {
            if(isWriterMethod) {
                oldVal = toStrFunc.apply(obj);
            }
            if (!Modifier.isAbstract(method.getModifiers())) {
                retValFromSuper = proxy.invokeSuper(obj, args);
            }
        } finally {
            if(isWriterMethod) {
                PropertyChangeEvent changeEvent = new PropertyChangeEvent(toStrFunc.apply(obj),
                        name +"[method: "+ method.getName() +"]", oldVal, toStrFunc.apply(args[0]));
                changeEvent.setPropagationId(System.identityHashCode(this));
                firePropertyChange(changeEvent);
            }
        }
        return retValFromSuper;
    }

    public static class CollectionPropertyChangeLogListener implements PropertyChangeListener {
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            logger.debug("集合属性变化监听，列表信息：{}，变更信息：[{}, oldVal:{}, arg:{}]",
                    evt.getSource(), evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
        }
    }
}
